// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "CasVideoUtil.h"
#include "CasLog.h"

namespace {
    const uint64_t DURATION_USEC = 1000000ULL;
    const uint64_t MEGA = 1000000ULL;
    const long KILO = 1000L;
}

CasVideoUtil *CasVideoUtil::g_instance = nullptr;
std::mutex CasVideoUtil::g_instanceLock;

/*
 * @fn GetInstance
 * @brief to get CasVideoUtil singleton
 */
CasVideoUtil *CasVideoUtil::GetInstance()
{
    std::lock_guard<std::mutex> lockGuard(g_instanceLock);
    if (g_instance == nullptr) {
        g_instance = new (std::nothrow) CasVideoUtil();
        if (g_instance == nullptr) {
            ERR("Failed to instantiate.");
            return nullptr;
        }
    }
    return g_instance;
}

/*
 * @fn DestroyInstance
 * @brief to release CasVideoUtil singleton
 */
void CasVideoUtil::DestroyInstance()
{
    std::lock_guard<std::mutex> lockGuard(g_instanceLock);
    if (g_instance != nullptr) {
        g_instance->Release();
        delete g_instance;
        g_instance = nullptr;
    }
}

/*
 * @fn Init
 * @brief to Init CasVideoUtil resource
 */
bool CasVideoUtil::Init()
{
    std::lock_guard<std::mutex> lockGuard(m_lock);
    if (m_frameQueue == nullptr) {
        m_frameQueue = new (std::nothrow) CasItemQueue<uint64_t>();
        if (m_frameQueue == nullptr) {
            ERR("Init error.");
            return false;
        }
    }
    return true;
}

/*
 * @fn GetNow
 * @brief to get the time in microsecond this moment.
 * @return uint64_t
 */
uint64_t CasVideoUtil::GetNow()
{
    struct timespec tv;
    clock_gettime(CLOCK_REALTIME, &tv);
    return static_cast<uint64_t>(tv.tv_sec * MEGA + tv.tv_nsec / KILO);
}

/*
 * @fn GetFps
 * @brief to get the realtime fps
 * @return uint32_t, fps
 */
uint32_t CasVideoUtil::GetFps()
{
    uint32_t fps = 0;
    std::lock_guard<std::mutex> lockGuard(m_lock);
    if (m_frameQueue == nullptr) {
        ERR("Frame queue is null, init first.");
        return 0;
    }
    if (!m_frameQueue->IsEmpty()) {
        auto now = GetNow();
        uint32_t index = 0;
        auto timeStamp = m_frameQueue->GetItemAt(index);
        while (timeStamp <= now) {
            if (now - timeStamp <= DURATION_USEC) {
                fps++;
            }
            if (m_frameQueue->GetItemNum() <= index + 1) {
                break;
            }
            index++;
            timeStamp = m_frameQueue->GetItemAt(index);
        }
    }
    return fps;
}

/*
 * @fn SetFps
 * @brief to push latest frame TS back into queue
 * @param[in] latest timeStamp of (type <tt>timestamp_t</tt>)
 */
void CasVideoUtil::SetTimestamp(uint64_t timeStamp)
{
    std::lock_guard<std::mutex> lockGuard(m_lock);
    if (m_frameQueue != nullptr) {
        m_frameQueue->PutItem(timeStamp);
    }
}


/*
 * @brief: construct
 */
CasVideoUtil::CasVideoUtil() = default;

/*
 * @brief: deconstruct
 */
CasVideoUtil::~CasVideoUtil()
{
    this->Release();
}

/*
 * @fn Release
 * @brief to release CasVideoUtil resource
 */
void CasVideoUtil::Release() noexcept
{
    if (m_frameQueue != nullptr) {
        m_frameQueue->ClearQueue();
        delete m_frameQueue;
        m_frameQueue = nullptr;
    }
}

void CasVideoUtil::DropOneFrame() {
    uint64_t currentTime = GetNow();

    m_VideoDropCount++;

    if (m_lastTimeRefreshDropFPS == 0) {
        m_lastTimeRefreshDropFPS = currentTime;
    }
    if (currentTime - m_lastTimeRefreshDropFPS > DURATION_USEC) {
        m_VideoDropFPS = m_VideoDropCount;
        m_VideoDropCount = 0;
        m_lastTimeRefreshDropFPS = currentTime;
    }
}

int CasVideoUtil::GetCurrentDropFPS() {
    uint64_t currentTime = GetNow();
    if (currentTime - m_lastTimeRefreshDropFPS > 2 * DURATION_USEC) {
        return 0;
    }
    return m_VideoDropFPS;
}